package io.github.wslxm.springbootplus2.manage.sys.handle;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.IdUtil;
import io.github.wslxm.springbootplus2.core.constant.BaseConstant;
import io.github.wslxm.springbootplus2.core.enums.BaseDic;
import io.github.wslxm.springbootplus2.manage.sys.dao.SysAuthorityDao;
import io.github.wslxm.springbootplus2.manage.sys.dao.SysRoleAuthDao;
import io.github.wslxm.springbootplus2.manage.sys.model.entity.SysAuthority;
import io.github.wslxm.springbootplus2.starter.redis.lock.XjDistributedLock;
import io.github.wslxm.springbootplus2.utils.XjAuthCacheKeyUtil;
import io.github.wslxm.springbootplus2.utils.XjUriUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 项目启动时 权限系统的所有接口权限并注册 处理器
 * <p>
 * 自动扫描整个系统的接口,并写入权限表中
 * </P>
 *
 * @author 王松
 * @mail 1720696548@qq.com
 * @date 2025/9/18 16:51
 */
@Component
@Slf4j
public class InitAuthScanningHandle {

    @Autowired
    private ApplicationContext context;
    @Autowired
    private SysAuthorityDao sysAuthorityDao;
    @Autowired
    private SysRoleAuthDao sysRoleAuthDao;

    /**
     * 顶级父id
     */
    private static final String PID = "0";

    /**
     * 接口自动扫描（1、项目启动时自动执行   2、设置了权限授权状态更新
     * <p>
     * 扫描添加接口信息，扫描启动类下的所有包
     * 存在修改（不修改原数据的禁用启动和权限状态,防止重启项目时修改被还原）
     * 不存在添加
     * 多余的生成
     * </p>
     *
     * @return void
     * @date 2019/11/25 0025 9:02
     */
    @Transactional(rollbackFor = Exception.class)
    @XjDistributedLock(waitTime = 0, leaseTime = 10L)
    public Boolean refreshAuthDb() {
        // 获取所有需要被扫描的文件
        log.info("  @.@...下方扫包错误信息正常现象，无需处理");
        Set<Class<?>> classSet = this.getClassSet(this.getPackages());
        log.info("  @.@...正在更新接口资源,所有被权限管理的接口将被打印出来…… ^.^ ");
        List<SysAuthority> list = sysAuthorityDao.list();
        // 扫描包，获得包下的所有类
        // 当前数据库已经存在的所有url权限列表--> key=url，value=对象，获取后移除Map中已取出, 最后剩下的全部删除
        Map<String, SysAuthority> authorityMap = new HashMap<>(16);
        if (list != null && list.size() > 0) {
            authorityMap = list.stream().collect(Collectors.toMap(item -> XjAuthCacheKeyUtil.get(item.getMethod(), item.getUrl()), item -> item));
        }
        // 所有需要修改list |添加list |删除的Ids
        List<SysAuthority> updAuth = new ArrayList<>();
        List<SysAuthority> addAuth = new ArrayList<>();
        List<String> delIds = new ArrayList<>();
        List<String> classList = new ArrayList<>();
        // 类上url
        List<String> classUrl = new ArrayList<>();
        // 开始解析
        this.startAnalysis(classSet, authorityMap, updAuth, addAuth, classUrl, classList);
        log.info("  检查多个类 URI 地址是否重复命名, 多个类的 URI 跟路径不能相同");
        classUrl.stream().collect(Collectors.toMap(item -> item, item -> item));
        log.info("  本次刷新接口+类 总数量为:{} ,如接口 [备注信息] 或 [请求方式] 发送改变,则已被刷新", updAuth.size());
        log.info("  本次添加接口+类 总量为:  {}", addAuth.size());
        // 修改
        if (updAuth.size() > 0) {
            sysAuthorityDao.updateBatchById(updAuth, 1024);
        }
        // 新增
        if (addAuth.size() > 0) {
            // 判断新增的接口中是否有重复的url，如果Url有重复直接将直接抛出异常
            log.info("   检查接口 URI 地址是否存在重复命名,不能存在多个【请求方式+请求uri】完全相同的URL");
            addAuth.stream().collect(Collectors.toMap(item -> XjAuthCacheKeyUtil.get(item.getMethod(), item.getUrl()), item -> item));
            // 添加权限
            sysAuthorityDao.saveBatch(addAuth, 1024);
        }
        // 删除
        if (authorityMap.size() > 0) {
            authorityMap.forEach((k, v) -> delIds.add(v.getId()));
            sysAuthorityDao.removeByIds(delIds);
            // 删除权限时同时删除权限和角色的绑定关系
            sysRoleAuthDao.delByAuthIds(delIds);
        }
        log.info("  本次删除接口+类 总数量为: {}", delIds.size());
        log.info("  当前被管理的 总类数量为:  {}", classList.size());
        log.info("  当前被管理的 总接口数量为: {}", ((updAuth.size() + addAuth.size()) - classList.size()));
        log.info("  @.@...");
        return true;
    }


    private void startAnalysis(Set<Class<?>> classSet, Map<String, SysAuthority> authorityMap, List<SysAuthority> updAuth, List<SysAuthority> addAuth, List<String> classUrl, List<String> classList) {
        for (Class<?> classInfo : classSet) {
            // 类上存在 @Api 注解 + @RequestMapping 的类进行下一步操作
            // 判断当前类是否需要保存到接口权限内（目前：PC_ADMIN=平台 / PC_USER  用户端）,代码生成器生成的代码默认(BaseConstant.InterfaceType.RELEASE)不需要任何权限可访问,需手动修改BaseConstant.InterfaceType. 参数
            String pUri = XjUriUtil.getClassUri(classInfo);
            if(StringUtils.isBlank(pUri)){
                continue;
            }
            // 获取类名/类注释
            Tag api = classInfo.getDeclaredAnnotation(Tag.class);
            String classDesc = classInfo.getSimpleName();
            String className = classDesc;
            if (api != null) {
                className = api.name();
                classDesc = api.description();
            }

            Integer uriType = getUriTypeByUrl(pUri);
            Integer state = getStateByUrl(pUri);
            boolean isSign = getIsSignByUrl(pUri);
            String classLog = "  接口类：--------------@.@[" + classDesc + "-" + className + "]--";
            log.info(String.format("%-100s", classLog).replace(" ", "-"));
            if (authorityMap.containsKey(XjAuthCacheKeyUtil.get("", pUri))) {
                // 存在修改
                SysAuthority updAuthority = authorityMap.get(XjAuthCacheKeyUtil.get("", pUri));
                updAuthority.setUrl(pUri);
                updAuthority.setPid(PID);
                updAuthority.setDesc(className);
                updAuthority.setType(uriType);
                updAuthority.setState(state);
                // 添加/处理方法上的权限
                this.putMethods(classInfo, authorityMap, updAuthority, updAuth, addAuth);
                updAuth.add(updAuthority);
                // 移除Map中已取出的数据
                authorityMap.remove(XjAuthCacheKeyUtil.get("", pUri));
            } else {
                // 不存在新添加
                SysAuthority addAuthority = new SysAuthority();
                addAuthority.setId(IdUtil.getSnowflakeNextIdStr());
                addAuthority.setPid(PID);
                addAuthority.setMethod("");
                addAuthority.setUrl(pUri);
                addAuthority.setDesc(className);
                addAuthority.setType(uriType);
                addAuthority.setState(state);
                addAuthority.setDisable(BaseDic.Whether.V0.getValue());
                addAuthority.setIsSign(isSign);
                // 添加/处理方法上的权限
                this.putMethods(classInfo, authorityMap, addAuthority, updAuth, addAuth);
                addAuth.add(addAuthority);
            }
            classUrl.add(pUri);
            //被管理的类数量
            classList.add(classDesc);
        }
        log.info("检查多个类 URI 地址是否重复命名, 多个类的 URI 跟路径不能相同");
        classUrl.stream().collect(Collectors.toMap(item -> item, item -> item));
        log.info("  本次刷新接口+类 总数量为:{} ,如接口 [备注信息] 或 [请求方式] 发送改变,则已被刷新", updAuth.size());
        log.info("  本次添加接口+类 总量为:  {}", addAuth.size());
    }


    /**
     * 获取启动类注解上需要扫描的路径
     *
     * @return {@link String[]}
     */
    private String[] getPackages() {
        // 获取启动类注解上需要扫描的路径
        Map<String, Object> annotatedBeans = context.getBeansWithAnnotation(SpringBootApplication.class);
        Class<?> aClass = annotatedBeans.values().toArray()[0].getClass();
        SpringBootApplication annotation = aClass.getAnnotation(SpringBootApplication.class);
        return annotation.scanBasePackages();
    }

    /**
     * 获取启动类注解上需要扫描的类
     *
     * @return {@link String[]}
     */
    private Set<Class<?>> getClassSet(String[] packages) {
        Set<Class<?>> classByPackageName = new HashSet<>();
        for (String aPackage : packages) {
            Set<Class<?>> classes = ClassUtil.scanPackage(aPackage);
            classByPackageName.addAll(classes);
        }
        return classByPackageName;
    }


    /**
     * 通过url获取uri类型
     *
     * @param url url
     * @return {@link Integer}
     */
    private Integer getUriTypeByUrl(String url) {
        Integer uriType = BaseDic.AuthorityType.V3.getValue();
        if (url.contains(BaseConstant.Uri.API_ADMIN)) {
            // 管理端 | 默认需登录
            uriType = BaseDic.AuthorityType.V0.getValue();
        } else if (url.contains(BaseConstant.Uri.API_CLIENT)) {
            // 用户端 | 默认无需登录
            uriType = BaseDic.AuthorityType.V1.getValue();
        } else if (url.contains(BaseConstant.Uri.API_OPEN)) {
            // 通用 | 默认无需登录
            uriType = BaseDic.AuthorityType.V2.getValue();
        }
        return uriType;
    }

    /**
     * 通过url获得状态
     *
     * @param url url
     * @return {@link Integer}
     */
    private Integer getStateByUrl(String url) {
        Integer state = BaseDic.AuthorityState.V0.getValue();
        if (url.contains(BaseConstant.Uri.API_ADMIN)) {
            // 管理端 | 默认需登录
            state = BaseDic.AuthorityState.V1.getValue();
        } else if (url.contains(BaseConstant.Uri.API_CLIENT)) {
            // 用户端 | 无需登录+ 无需授权(已无授权功能)
            state = BaseDic.AuthorityState.V0.getValue();
        } else if (url.contains(BaseConstant.Uri.API_OPEN)) {
            // 通用 | 默认需登录+ 需授权(已无授权功能)
            state = BaseDic.AuthorityState.V1.getValue();
        }
        return state;
    }

    /**
     * 通过url获得状态
     *
     * @param url url
     * @return {@link Integer}
     */
    private boolean getIsSignByUrl(String url) {
        boolean isSign = false;
        if (url.contains(BaseConstant.Uri.API_ADMIN) || url.contains(BaseConstant.Uri.API_OPEN)) {
            isSign = true;
        }
        return isSign;
    }



    /**
     * 添加指定类的所有接口权限到 athorityList
     *
     * @param classInfo    当前类
     * @param authorityMap 当前数据库存在权限
     * @param authority    类的权限数据
     * @return void
     * @date 2019/11/25 0025 9:02
     */
    private void putMethods(Class<?> classInfo, Map<String, SysAuthority> authorityMap, SysAuthority authority, List<SysAuthority> updAuth, List<SysAuthority> addAuth) {
        // 获取类的所有方法循环添加方法级权限
        for (Method method : classInfo.getDeclaredMethods()) {
            // 获取 url | 请求方式
            Dict dict = XjUriUtil.getMethodUri(authority.getUrl(), method);
            String uri = dict.getStr("uri");
            String requestMethod = dict.getStr("requestMethod");
            if (uri == null) {
                continue;
            }
            // 获取swagger注释
            Operation apiOperation = method.getAnnotation(Operation.class);
            String desc = method.getName();
            if (apiOperation != null) {
                desc = apiOperation.summary();
            }
            // 日志输出, 使用占位方式让日志对齐
            log.info("  接口资源：[{}]  -->  [{}]  -->  [{}] ", String.format("%-6s", requestMethod), String.format("%-40s", uri), desc);
            /// 存在修改，不存在新添加
            if (authorityMap.containsKey(XjAuthCacheKeyUtil.get(requestMethod, uri))) {
                SysAuthority updAuthority = authorityMap.get(XjAuthCacheKeyUtil.get(requestMethod, uri));
                updAuthority.setPid(authority.getId());
                updAuthority.setDesc(desc);
                updAuthority.setUrl(uri);
                updAuthority.setMethod(requestMethod);
                updAuthority.setType(authority.getType());
                updAuth.add(updAuthority);
                // 移除Map中已取出的数据
                authorityMap.remove(XjAuthCacheKeyUtil.get(requestMethod, uri));
            } else {
                SysAuthority addAuthority = new SysAuthority();
                addAuthority.setId(IdUtil.getSnowflakeNextIdStr());
                addAuthority.setPid(authority.getId());
                addAuthority.setDesc(desc);
                addAuthority.setUrl(uri);
                addAuthority.setMethod(requestMethod);
                addAuthority.setType(authority.getType());
                addAuthority.setState(authority.getState());
                addAuthority.setDisable(authority.getDisable());
                addAuthority.setIsSign(authority.getIsSign());
                addAuth.add(addAuthority);
            }
        }
    }

}
